import * as sinon from 'sinon';
import { expect } from 'chai';
import axios from 'axios';
import * as step from '@flow-step/step-toolkit';
import { artifactUpload, uploadMetaData } from '../src/package-upload';
import { UploadInputs } from '../src/upload-inputs';
import mock from 'mock-fs'
let artifact ={} as UploadInputs;
describe('Artifact Upload and MetaData Upload', () => {
    let sandbox: sinon.SinonSandbox;
    let axiosPostStub: sinon.SinonStub;
    let stepInfoStub: sinon.SinonStub;
    let stepErrorStub: sinon.SinonStub;

    beforeEach(() => {
        sandbox = sinon.createSandbox()
        axiosPostStub = sandbox.stub(axios, 'request')
        stepInfoStub = sandbox.stub(step, 'info')
        stepErrorStub = sandbox.stub(step, 'error')
        mock({
            '/path/to': {
                'artifact': 'file content',
            },
        });
         artifact = {
            artifactFullName: '/path/to/artifact',
            artifact: 'test-artifact',
            artifactName: 'artifact-name',
            artifactVersion: '1.0.0',
            fileMD5: 'md5hash',
            credential: 'test-credential',
            packageHost: 'http://testhost',
            orgId: 'org1',
            repoId: 'repo1',
            stepIdentifier: 'step1',
            fileId: 'file1',
            bucketName: 'bucket1',
            fileParentId: 'parent1',
            uploadType: 'type1'
        } as UploadInputs;
        process.env['ENGINE_GLOBAL_PARAM_ORGANIZATION_ID'] = 'org1';
    });

    afterEach(() => {
        sandbox.restore();
        mock.restore()
    });

    describe('artifactUpload', () => {


        it('should fail if credential is undefined', async () => {
            artifact.credential = undefined;

            try {
                await artifactUpload(artifact);
                expect.fail('Expected an error to be thrown');
            } catch (error) {
                // @ts-ignore
                expect(error.message).to.equal('packages user credential is empty');
                expect(stepErrorStub.calledWith('Failed to get packages user credential, please check if the service connection is invalid!')).to.be.true;
            }
        });

        it('should upload artifact successfully and validate MD5', async () => {
            const mockResponse = {
                data: {
                    object: {
                        url: 'http://testurl',
                        fileMd5: 'md5hash',
                        fileSize: 1024
                    }
                }
            };

            axiosPostStub.resolves(mockResponse);

            await artifactUpload(artifact);

            expect(axiosPostStub.calledOnce).to.be.true;
            expect(stepInfoStub.calledWith(`Upload ${artifact.artifactFullName} to PACKAGES, and artifact version is ${artifact.artifactVersion}`)).to.be.true;
            expect(stepInfoStub.calledWith('Artifact download URL is: http://testurl')).to.be.true;
            expect(stepInfoStub.calledWith('Upload finished')).to.be.true;
        });

        it('should handle MD5 mismatch error', async () => {
            const mockResponse = {
                data: {
                    object: {
                        url: 'http://testurl',
                        fileMd5: 'different-md5',
                        fileSize: 1024
                    }
                }
            };

            axiosPostStub.resolves(mockResponse);

            try {
                await artifactUpload(artifact);
                expect.fail('Expected an error to be thrown');
            } catch (error) {
                // @ts-ignore
                expect(error.message).to.equal('Failed to upload artifact');
                expect(stepErrorStub.calledWith('Md5 not matched, source fileMd5:md5hash, upload fileMd5:different-md5')).to.be.true;
            }
        });

        it('should handle upload failure', async () => {
            const mockError = new Error('Upload failed');
            axiosPostStub.rejects(mockError);

            try {
                await artifactUpload(artifact);
                expect.fail('Expected an error to be thrown');
            } catch (error) {
                // @ts-ignore
                expect(error.message).to.equal('Failed to upload artifact');
            }
        });
    });

    describe('uploadMetaData', () => {
        const artifact = {
            artifact: 'test-artifact',
            artifactVersion: '1.0.0',
            fileMD5: 'md5hash',
            credential: 'test-credential',
            packageHost: 'http://testhost',
            regionId: 'region1',
            repoId: 'repo1',
            metaUpload: 'YES'
        } as UploadInputs;

        it('should upload metadata successfully using old architecture', async () => {
            const mockResponse = { status: 200, data: 'Success' };
            axiosPostStub.resolves(mockResponse);

            await uploadMetaData(artifact);

            expect(axiosPostStub.calledOnce).to.be.true;
            expect(stepInfoStub.calledWith('MetaData upload result is: 200, Success')).to.be.true;
        });

        it('should upload metadata successfully using new architecture', async () => {
            const mockOldResponse = { status: 404, data: 'Not Found' };
            const mockNewResponse = { status: 200, data: 'Success' };
            axiosPostStub.onFirstCall().rejects(mockOldResponse).onSecondCall().resolves(mockNewResponse);

            await uploadMetaData(artifact);

            expect(axiosPostStub.callCount).to.equal(1);
        });

        it('should handle error during metadata upload', async () => {
            const mockError = { response: { status: 500, data: 'Server Error' } };
            axiosPostStub.rejects(mockError);

            await uploadMetaData(artifact);

            expect(stepErrorStub.calledWith('request error: [object Object]')).to.be.true;
        });

        it('should skip metadata upload if metaUpload is NO', async () => {
            artifact.metaUpload = 'NO';

            await uploadMetaData(artifact);

            expect(axiosPostStub.notCalled).to.be.true;
        });
    });
});
